/******************************************************************************************************
Copyright (C) 2017 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals BaseConverter, NMRSpectrum, NMRSpectrumUtils, File, TextStream, Env, mainWindow, Dir, print*/
/*jslint plusplus: true, indent: 4*/

/** Default constructor of the converter
 * Defines the file filter
*/
function ASCIIMatrixConverter() {
	"use strict";
	BaseConverter.call(this);
	this.exportFileFilter = "NMR CSV Matrix (Transposed) (*.csv *.txt)"; //!< It will override any other script converter with the same exportFileFilter
}

ASCIIMatrixConverter.prototype = new BaseConverter();

/** Can export a Spectra collection
 */
ASCIIMatrixConverter.prototype.wantsExport = function (aPageItems) {
	"use strict";
	var i, dim, nmrSpc, nmrUtils,
		specs = [];

	dim = this.getDim(aPageItems);
	for (i = 0; i < aPageItems.length; i++) {
		if (aPageItems[i].name !== "NMR Spectrum") {
			return false;
		}
		nmrSpc = new NMRSpectrum(aPageItems[i]);
		if (!nmrSpc.isValid() || nmrSpc.dimCount !== dim) {
			return false;
		}
		specs.push(nmrSpc);
	}
	nmrUtils = new NMRSpectrumUtils();
	return nmrUtils.overlayCompatibility(specs);
};

/** Get Spectra dimension
 */
ASCIIMatrixConverter.prototype.getDim = function (aPageItems) {
	"use strict";
	var i, nmrSpc, nmrUtils,
		dim = 0;

	for (i = 0; i < aPageItems.length; i++) {
		if (aPageItems[i].name !== "NMR Spectrum") {
			return -1;
		}
		nmrSpc = new NMRSpectrum(aPageItems[i]);
		if (dim === 0) {
			dim = nmrSpc.dimCount;
		}
		if (!nmrSpc.isValid() || nmrSpc.dimCount !== dim) {
			return -1;
		}
	}
	return dim;
};

/** Writes the file
Exports a spectra collection as an ASCII matrix. Returns true if the file aFilename is generated correctly.
 */
ASCIIMatrixConverter.prototype.exportItems = function (aPageItems, aFilename) {
	"use strict";
	var t0, t1, dim, retVal = 0;

	dim = this.getDim(aPageItems);

	mnDebug("Exporting to CSV.......");
	t0 = new Date();

	if (dim === 1) {
		retVal = this.export1DItems(aPageItems, aFilename);
	} else if (dim === 2) {
		retVal = this.export2DItems(aPageItems, aFilename);
	}

	t1 = new Date();
	mnDebug("Time to export: " + (t1-t0)/1000 + " seconds");

	return retVal;
};

/** Writes the file
Exports a 1D Spectra collection as an ASCII matrix. Returns true if the file aFilename is generated correctly.
 */
ASCIIMatrixConverter.prototype.export1DItems = function (aPageItems, aFilename) {
	"use strict";
	var i, s, v, n, N,
		nmrSpec, spectra, nmrUtils, file, strm, precision,
		scaleBounds, fullBounds, fullMin, fullMax, min, max,
		step, distance, pos, posIt,
		dim = 1,
		str = "",
		specs = [],
		intensities = [],
		mask = [],
		isArrayed = false,
		activeSpec = 0;

	// Get spectra
	for (i = 0; i < aPageItems.length; i++) {
		nmrSpec = new NMRSpectrum(aPageItems[i]);
		if (nmrSpec.isValid() && nmrSpec.dimCount === dim) {
			if (nmrSpec.specCount > 1) { //isArrayed
				isArrayed = true;
				activeSpec = nmrSpec.curSpecIndex;
				spectra = nmrSpec.spectra;
				for (s = 0; s < spectra.length; s++) {
					specs.push(spectra[s]);
				}
			} else {
				specs.push(nmrSpec);
			}
		}
	}
	if (!specs.length) {
		return 0;
	}

	file = new File(aFilename);
	if (file.open(File.WriteOnly)) {
		strm = new TextStream(file);
		precision = strm.precision;

		// Get ranges
		nmrUtils = new NMRSpectrumUtils();
		scaleBounds = nmrUtils.scaleBoundaries(dim, specs);
		min = scaleBounds.min;
		max = scaleBounds.max;
		if (min > max) {
			v = max;
			max = min;
			min = v;
		}

		fullBounds = nmrUtils.fullScaleBoundaries(dim, specs);
		fullMin = fullBounds.min;
		fullMax = fullBounds.max;
		if (fullMin > fullMax) {
			v = fullMax;
			fullMax = fullMin;
			fullMin = v;
		}

		//min and max must be inside full scale
		min = Math.max(fullMin, min);
		max = Math.min(fullMax, max);
		
		fullMin = Math.min(fullMin, min);
		fullMax = Math.max(fullMax, max);
		step = (fullMax - fullMin) / nmrUtils.getMaxNumberPoints(dim, specs);

		distance = Math.abs(min - fullMin);
		n = Math.floor(distance / step);
		pos = fullMin + n * step;
		while (pos < min) {
			pos += step;
		}
		max += 0.25 * step; // Condition (pos-max < step/4) -> sometimes, because of calculations, the last point is not exported
		N = Math.floor((max - pos)/step) + 1;

		// Write headers and positions
		nmrSpec = specs[0];
		str = "#,Title,Class";
		posIt = pos;
		for (i = 0; i < N; i++) {
			str += "," + posIt.toPrecision(precision).toString();
			posIt += step;
		}
		strm.writeln(str);

		// Get intensities array and visible/hidden mask array
		for (s = 0; s < specs.length; s++) {
			nmrSpec = specs[s];
			nmrSpec.curSpecIndex = s;
			intensities.push(nmrSpec.rangeIntensities(pos, max, step));
			mask.push(nmrSpec.visibleMask(pos, max, step));
		}

		// Write values with spectra in rows and intensities in columns
		for (s = 0; s < specs.length; s++) {
			nmrSpec = specs[s];
			nmrSpec.curSpecIndex = s; // UnitsToUnits uses the active spectrum always. Fixed bug #5019.
			str = (s + 1).toString() + "," + nmrSpec.title + "," + nmrSpec.className;
			for (i = N - 1; i >= 0; i--) {
				str += ",";
				if (mask[s][i]) {
					str += intensities[s][i].toPrecision(precision).toString();
				}
			}
			strm.writeln(str);
		}

		if (isArrayed) { //recover the old active spectrum index
			nmrSpec.curSpecIndex = activeSpec;
		}

		file.close();
		return 0;
	}

	return 1;
};

/** Writes the file
Exports a 2D Spectra collection as an ASCII matrix. Returns true if the file aFilename is generated correctly.
 */
ASCIIMatrixConverter.prototype.export2DItems = function (aPageItems, aFilename) {
	"use strict";
	var i, j, s, v, NF1, NF2,
		nmrSpec, spectra, nmrUtils, file, strm, precision,
		scaleBounds, fullBounds, fullMinF1, fullMinF2, fullMaxF1, fullMaxF2, minF1, minF2, maxF1, maxF2,
		stepF1, stepF2, distanceF1, distanceF2, nF1, nF2, posF1, posF2, posItF1, posItF2,
		dim = 2,
		str = "",
		specs = [],
		intensities = [],
		mask = [],
		isArrayed = false,
		activeSpec = 0;

	// Get spectra
	for (i = 0; i < aPageItems.length; i++) {
		nmrSpec = new NMRSpectrum(aPageItems[i]);
		if (nmrSpec.isValid() && nmrSpec.dimCount === dim) {
			if (nmrSpec.specCount > 1) { // isArrayed
				isArrayed = true;
				activeSpec = nmrSpec.curSpecIndex;
				spectra = nmrSpec.spectra;
				for (s = 0; s < spectra.length; s++) {
					specs.push(spectra[s]);
				}
			} else {
				specs.push(nmrSpec);
			}
		}
	}
	if (!specs.length) {
		return 0;
	}

	file = new File(aFilename);
	if (file.open(File.WriteOnly)) {
		strm = new TextStream(file);
		precision = strm.precision;

		// Get F1 values
		nmrUtils = new NMRSpectrumUtils();
		scaleBounds = nmrUtils.scaleBoundaries(1, specs, true);
		minF1 = scaleBounds.min;
		maxF1 = scaleBounds.max;
		if (minF1 > maxF1) {
			v = maxF1;
			maxF1 = minF1;
			minF1 = v;
		}
		fullBounds = nmrUtils.fullScaleBoundaries(1, specs);
		fullMinF1 = fullBounds.min;
		fullMaxF1 = fullBounds.max;
		if (fullMinF1 > fullMaxF1) {
			v = fullMaxF1;
			fullMaxF1 = fullMinF1;
			fullMinF1 = v;
		}
		stepF1 = (fullMaxF1 - fullMinF1) / nmrUtils.getMaxNumberPoints(1, specs);
		distanceF1 = Math.abs(minF1 - fullMinF1);
		nF1 = Math.floor(distanceF1 / stepF1);
		posF1 = fullMinF1 + nF1 * stepF1;
		while (posF1 < minF1) {
			posF1 += stepF1;
		}

		// Get F2 values
		scaleBounds = nmrUtils.scaleBoundaries(2, specs, true);
		minF2 = scaleBounds.min;
		maxF2 = scaleBounds.max;
		if (minF2 > maxF2) {
			v = maxF2;
			maxF2 = minF2;
			minF2 = v;
		}
		fullBounds = nmrUtils.fullScaleBoundaries(2, specs);
		fullMinF2 = fullBounds.min;
		fullMaxF2 = fullBounds.max;
		if (fullMinF2 > fullMaxF2) {
			v = fullMaxF2;
			fullMaxF2 = fullMinF2;
			fullMinF2 = v;
		}
		stepF2 = (fullMaxF2 - fullMinF2) / nmrUtils.getMaxNumberPoints(2, specs);
		distanceF2 = Math.abs(minF2 - fullMinF2);
		nF2 = Math.floor(distanceF2 / stepF2);
		posF2 = fullMinF2 + nF2 * stepF2;
		while (posF2 < minF2) {
			posF2 += stepF2;
		}

		// Condition (pos-max < step/4) -> sometimes, because of calculations, the last point is not exported.
		maxF1 += 0.25 * stepF1;
		maxF2 += 0.25 * stepF2;
		NF1 = Math.floor((maxF1 - posF1)/stepF1) + 1;
		NF2 = Math.floor((maxF2 - posF2)/stepF2) + 1;

		// Write headers and positions. Coordinates format: [f2_coord:f1_coord] ppm
		nmrSpec = specs[0];
		str = "#,Title,Class";
		posItF1 = posF1;
		for (i = 0; i < NF1; i++) {
			posItF2 = posF2;
			for (j = 0; j < NF2; j++) {
				str += "," + "[" + posItF2.toPrecision(precision).toString() + ":" + posItF1.toPrecision(precision).toString() + "]";
				posItF2 += stepF2;
			}
			posItF1 += stepF1;
		}
		strm.writeln(str);

		// Get intensities array and visible/hidden mask array
		for (s = 0; s < specs.length; s++) {
			nmrSpec = specs[s];
			nmrSpec.curSpecIndex = s;
			intensities.push(nmrSpec.rangeIntensities(posF2, maxF2, stepF2, posF1, maxF1, stepF1));
			mask.push(nmrSpec.visibleMask(posF2, maxF2, stepF2, posF1, maxF1, stepF1));
		}

		// Write values with spectra in rows and intensities in columns
		for (s = 0; s < specs.length; s++) {
			nmrSpec = specs[s];
			nmrSpec.curSpecIndex = s; // UnitsToUnits uses the active spectrum always. Fixed bug #5019.
			str = (s + 1).toString() + "," + nmrSpec.title + "," + nmrSpec.className;
			for (i = NF1 - 1; i >= 0; i--) {
				for (j = NF2 - 1; j >= 0; j--) {
					str += ",";
					if (mask[s][i][j]) {
						str += intensities[s][i][j].toPrecision(precision).toString();
					}
				}
			}
			strm.writeln(str);
		}

		if (isArrayed) { //recover the old active spectrum index
			nmrSpec.curSpecIndex = activeSpec;
		}

		file.close();
		return 0;
	}

	return 1;
};

/** Add to the Env.Converters environment engine
 */
Env.Converters.push(new ASCIIMatrixConverter());

//test
ASCIIMatrixConverter.ASCIIMatrixConverterTest = function () {
	"use strict";

	var retVal = -1,
		converter = new ASCIIMatrixConverter(),
		pageItems = mainWindow.activeDocument.selection(),
		file = Dir.home() + "/ASCIIMatrix.txt",
		dim = converter.getDim(pageItems);

	if (converter.wantsExport(pageItems)) {
		retVal = converter.exportItems(pageItems, file);
	}
	print(retVal + " (" + file + ")");
	return retVal;
};

Env.NMRBinningExport = function (aItem, aFilename) {
	'use strict';
	var converter = new ASCIIMatrixConverter(),
		dim = converter.getDim([aItem]);

	if (converter.wantsExport([aItem])) {
		return converter.exportItems([aItem], aFilename);
	}
	return false;
};
